// --- Copyright (c) 2010 Steven Hartgers ---
// Copyright (C) 2010 Steven Hartgers All rights are reserved.
// Author contact: fiinix00/at\gmail.com
// ------------------------------------------------------------------------
// This program is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
//  
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more detail
//  
// You should have received a copy of the GNU General Public License 
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
// ------------------------------------------------------------------------
// COPYRIGHT NOTICE: This copyright notice may NOT be removed, obscured or modified 
// without written consent from the author.
// --- End copyright notice --- 



using System;
using System.Threading;
using System.Collections;

namespace Hack.Net.Smtp
{
    /**
     * A standard semahpore implementation.
     */
    public class Semaphore
    {
        /** The initial value of semaphore */
        public readonly int init;

        /** The current counter. */
        private int count;

        /** Mutual exclusion */
        Mutex mut = new Mutex();

        /** The events to wake up when finished. Queues are thread-safe. */
        Queue waitEvents = new Queue();


        /** Create default semaphore with initial counter=1 */
        public Semaphore()
        {
            this.init = 1;
            this.count = init;
        }

        /** Create a semaphore with specific intial counter */
        public Semaphore(int init)
        {
            this.init = init;
            this.count = init;
        }


        /** Aquire (decrement) semaphore. If counter gets negative, sleep. */
        public void Aquire()
        {
            //Enter CS
            //This is a critical section, because two could decrement
            //count and after that both go to sleep where just one
            //should.
            mut.WaitOne();

            //Decrement counter
            count--;

            //Wait...
            if (count < 0)
            {
                //Enqueue wait object...
                AutoResetEvent evt = new AutoResetEvent(false);
                waitEvents.Enqueue(evt);

                //Exit CS
                mut.ReleaseMutex();

                //Wait until ready
                evt.WaitOne();
            }
            else
            {
                //Exit CS
                mut.ReleaseMutex();
            }
        }


        /** Release (increment) semaphore. If counter is negative, wake a thread up. */
        public void Release()
        {
            //Enter CS
            //This is a critical section, because if init>1 and count=-1, two could
            //have been aquired above and now both call Release and check count<0.
            //Consequently both could wake up a sleeping thread where only one is.
            //
            //This can not happen, if init=1. Then the number of sleeping threads is
            //always -count and exactly one thread has called Aquire(). So a semaphore
            //with init=1 simulates a critical section.
            mut.WaitOne();


            //We have to release a thread...
            if (count < 0)
            {
                //Wake up one that waited longest time
                AutoResetEvent evt = (AutoResetEvent)waitEvents.Dequeue();
                evt.Set();
            }

            //Increment counter
            count++;


            //Exit CS
            mut.ReleaseMutex();
        }
    }


}